/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <X11/Xlib.h>
#include <X11/extensions/sync.h>
#include <libxml/xinclude.h>
#include <glib.h>
#include "../nls.h"
#include "../toolkit/keyboard.h"
#include "../toolkit/xml.h"
#include "misc.h"
#include "geom.h"
#include "frame.h"
#include "actions.h"
#include "actions/all.h"
#include "screen.h"
#include "focus.h"
#include "event.h"
#include "grab.h"
#include "window.h"
#include "stacking.h"
#include "client.h"
#include "config.h"
#include "place.h"
#include "moveresize.h"
#include "debug.h"
static struct wm_actions_act *interactive_act = NULL;
static guint interactive_initial_state = 0;
static GSList *registered = NULL;

static void definition_ref(struct wm_actions_definition *def)
{
	++def->ref;
}

static void definition_unref(struct wm_actions_definition *def)
{
	if(def && --def->ref == 0) {
		g_free(def->name);
		g_slice_free(struct wm_actions_definition, def);
	}
}

static void act_ref(struct wm_actions_act *act)
{
	++act->ref;
}

void actions_act_unref(struct wm_actions_act *act)
{
	if(act && --act->ref == 0) {
		if(act->def->free)
			act->def->free(act->options);
		definition_unref(act->def);
		g_slice_free(struct wm_actions_act, act);
	}
}

void actions_startup(void)
{
	action_all_startup();
}

void actions_shutdown(void)
{
	actions_interactive_cancel_act();
	while(registered) {
		struct wm_actions_definition *d = registered->data;
		if(d->shutdown)
			d->shutdown();
		definition_unref(d);
		registered = g_slist_delete_link(registered, registered);
	}
}

static struct wm_actions_definition *do_register(const gchar *name, wm_actions_data_free_func free,
	wm_actions_run_func run)
{
	GSList *it;
	struct wm_actions_definition *def;
	g_assert(run != NULL);
	for(it = registered; it; it = g_slist_next(it)) {
		def = it->data;
		if(!g_ascii_strcasecmp(name, def->name))
			return NULL;
	}
	def = g_slice_new(struct wm_actions_definition);
	def->ref = 1;
	def->name = g_strdup(name);
	def->free = free;
	def->run = run;
	def->shutdown = NULL;
	registered = g_slist_prepend(registered, def);
	return def;
}

gboolean actions_register_i(const gchar *name, wm_actions_i_data_setup_func setup, wm_actions_data_free_func free,
	wm_actions_run_func run)
{
	struct wm_actions_definition *def = do_register(name, free, run);
	if(def) {
		def->canbeinteractive = TRUE;
		def->setup.i = setup;
	}
	return def != NULL;
}

gboolean actions_register(const gchar *name, wm_actions_data_setup_func setup, wm_actions_data_free_func free,
	wm_actions_run_func run)
{
	struct wm_actions_definition *def = do_register(name, free, run);
	if(def) {
		def->canbeinteractive = FALSE;
		def->setup.n = setup;
	}
	return def != NULL;
}

static struct wm_actions_act *build_act_from_string(const gchar *name)
{
	GSList *it;
	struct wm_actions_definition *def = NULL;
	struct wm_actions_act *act = NULL;
	for(it = registered; it; it = g_slist_next(it)) {
		def = it->data;
		if(!g_ascii_strcasecmp(name, def->name))
			break;
		def = NULL;
	}
	if(def) {
		act = g_slice_new(struct wm_actions_act);
		act->ref = 1;
		act->def = def;
		definition_ref(act->def);
		act->i_pre = NULL;
		act->i_input = NULL;
		act->i_cancel = NULL;
		act->i_post = NULL;
		act->options = NULL;
	} else
		g_message(_("Invalid action \"%s\" requested. No such action exists."), name);
	return act;
}

struct wm_actions_act *actions_parse_string(const gchar *name)
{
	struct wm_actions_act *act = NULL;
	if((act = build_act_from_string(name))) {
		if(act->def->canbeinteractive) {
			if(act->def->setup.i)
				act->options = act->def->setup.i(NULL, &act->i_pre, &act->i_input, &act->i_cancel, &act->i_post);
		} else {
			if(act->def->setup.n)
				act->options = act->def->setup.n(NULL);
		}
	}
	return act;
}

struct wm_actions_act *actions_parse(xmlNodePtr node)
{
	gchar *name;
	struct wm_actions_act *act = NULL;
	if(t_xml_attr_string(node, "name", &name)) {
		if((act = build_act_from_string(name))) {
			if(act->def->canbeinteractive) {
				if(act->def->setup.i)
					act->options = act->def->setup.i(node->children, &act->i_pre, &act->i_input, &act->i_cancel, &act->i_post);
			} else {
				if(act->def->setup.n)
					act->options = act->def->setup.n(node->children);
			}
		}
		g_free(name);
	}
	return act;
}

gboolean actions_act_is_interactive(struct wm_actions_act *act)
{
	return act->i_input != NULL;
}

static void actions_setup_data(struct wm_actions_data *data, enum wm_user_action uact, guint state, gint x, gint y,
	gint button, enum wm_frame_context con, struct wm_client *client)
{
	data->uact = uact;
	data->state = state;
	data->x = x;
	data->y = y;
	data->button = button;
	data->context = con;
	data->client = client;
}

static gboolean actions_interactive_begin_act(struct wm_actions_act *act, guint state)
{
	if(grab_keyboard()) {
		interactive_act = act;
		act_ref(interactive_act);
		interactive_initial_state = t_keyboard_only_modmasks(state);
		event_halt_focus_delay();
		return TRUE;
	} else
		return FALSE;
}

static void actions_interactive_end_act(void)
{
	if(interactive_act) {
		struct wm_actions_act *ia = interactive_act;
		interactive_act = NULL;
		ungrab_keyboard();
		if(ia->i_post)
			ia->i_post(ia->options);
		actions_act_unref(ia);
	}
}

void actions_run_acts(GSList *acts, enum wm_user_action uact, guint state, gint x, gint y, gint button,
	enum wm_frame_context con, struct wm_client *client)
{
	GSList *it;
	gboolean update_user_time;
	if(x < 0 && y < 0)
		screen_pointer_pos(&x, &y);
	update_user_time = FALSE;
	for(it = acts; it; it = g_slist_next(it)) {
		struct wm_actions_data data;
		struct wm_actions_act *act = it->data;
		gboolean ok = TRUE;
		actions_setup_data(&data, uact, state, x, y, button, con, client);
		if(!interactive_act || interactive_act->def->run != act->def->run) {
			if(actions_act_is_interactive(act)) {
				if(interactive_act)
					actions_interactive_cancel_act();
				if(act->i_pre)
					if(!act->i_pre(state, act->options))
						act->i_input = NULL;
			}
			if(actions_act_is_interactive(act))
				ok = actions_interactive_begin_act(act, state);
		}
		if(ok) {
			if(!act->def->run(&data, act->options)) {
				if(actions_act_is_interactive(act))
					actions_interactive_end_act();
				if(client && client == focus_client)
					update_user_time = TRUE;
			} else {
				g_assert(act->i_input);
				break;
			}
		}
	}
	if(update_user_time)
		event_update_user_time();
}

gboolean actions_interactive_act_running(void)
{
	return interactive_act != NULL;
}

void actions_interactive_cancel_act(void)
{
	if(interactive_act) {
		if(interactive_act->i_cancel)
			interactive_act->i_cancel(interactive_act->options);
		actions_interactive_end_act();
	}
}

gboolean actions_interactive_input_event(XEvent *e)
{
	gboolean used = FALSE;
	if(interactive_act) {
		if(!interactive_act->i_input(interactive_initial_state, e, grab_input_context(), interactive_act->options, &used)) {
			used = TRUE;
			actions_interactive_end_act();
		}
	}
	return used;
}

void actions_client_move(struct wm_actions_data *data, gboolean start)
{
	static gulong ignore_start = 0;
	if(start)
		ignore_start = event_start_ignore_all_enters();
	else if(config_focus_follow && data->context != WM_FRAME_CONTEXT_CLIENT) {
		if(data->uact == WM_USER_ACTION_MOUSE_PRESS) {
			struct wm_client *c;
			if(!grab_on_pointer()) {
				if((c = client_under_pointer()) && c != data->client) {
					wm_debug_type(WM_DEBUG_FOCUS, "Generating fake enter because we did a mouse-event action");
					event_enter_client(c);
				} else if(!c && c != data->client) {
					wm_debug_type(WM_DEBUG_FOCUS, "Generating fake leave because we did a mouse-event action");
					event_enter_client(data->client);
				}
			}
		} else if(!data->button && !config_focus_under_mouse)
			event_end_ignore_all_enters(ignore_start);
	}
}
